package com.ticket.sass.common.ticketserver.platform.cx30

import com.google.gson.reflect.TypeToken
import com.ticket.sass.common.ticketserver.ApiInterface
import com.ticket.sass.common.ticketserver.configure.ConfigData
import com.ticket.sass.common.ticketserver.dto.input.*
import com.ticket.sass.common.ticketserver.dto.output.*
import com.ticket.sass.common.ticketserver.enums.OrderTicketStatus
import com.ticket.sass.common.ticketserver.enums.SeatStatus
import com.ticket.sass.common.ticketserver.exceptions.ApiStatusException
import com.ticket.sass.common.ticketserver.exceptions.ParseDataException
import com.ticket.sass.common.ticketserver.format
import com.ticket.sass.common.ticketserver.platform.cx30.response.*
import com.ticket.sass.common.ticketserver.platform.cx30.response.SubmitOrder.SubmitOrderResult.SeatInfos.SeatInfo
import com.ticket.sass.common.ticketserver.toJson
import com.ticket.sass.common.ticketserver.toLocalDateTime
import org.apache.axis.encoding.XMLType
import org.springframework.stereotype.Component


/**
 * @author 朱其鹏
 * @date 2024/5/17 11:07
 */
@Component("ticketserver.cx30.ApiInterfaceImpl")
class ApiInterfaceImpl : ApiInterface() {
    /**
     * 配置数据
     */
    val configData
        get() = config.data as ConfigData.CX30

    override fun schedules(input: ScheduleInput): ScheduleOutput {
        val params = listOf(
            RequestClient.Paramter("pPlanDate", XMLType.XSD_STRING, input.startDate),
        )
        val data = RequestClient.send("QueryPlanInfo", params, configData)
            .parseJson(ScheduleModel::class.java)
            .also {
                it.queryPlanInfoResult.checkApiStatus()
            }

        val filmInfo = queryFilmInfoByDatePeriod(input.startDate, input.endDate)
            .queryFilmInfoByDatePeriodResult.filmInfoVOs.filmInfoVO

        val filmDataGroup = try {
            filmInfo.toJson().parseJson(object : TypeToken<List<QueryFilmList.QueryFilmInfoByDatePeriodResult.FilmInfoVO>>() {})
        } catch (e: ParseDataException) {
            listOf(filmInfo.toJson().parseJson(object : TypeToken<QueryFilmList.QueryFilmInfoByDatePeriodResult.FilmInfoVO>() {}))
        }.groupBy { it.filmCode }

        val hallGroup = halls().list.groupBy { it.code }

        return ScheduleOutput(
            list = data.queryPlanInfoResult.cinemaPlans?.cinemaPlan?.map {
                val endTime = it.startTime.toLocalDateTime("yyyy-MM-dd HH:mm:ss")
                    .plusMinutes(it.films.film.duration.toLong())
                    .format("yyyy-MM-dd HH:mm:ss")

                ScheduleOutput.Schedule(
                    sessionCode = it.featureAppNo,
                    scheduleKey = "",
                    startTime =  it.startTime,
                    endTime = endTime,
                    status = "",
                    filmName = it.films.film.filmName,
                    filmCode = it.films.film.filmCode,
                    filmVersion = filmDataGroup[it.films.film.filmCode]?.firstOrNull()?.version ?: "",
                    hallName = hallGroup[it.screenCode]?.firstOrNull()?.name ?: "",
                    hallCode = it.screenCode,
                    salePrice = it.price?.listingPrice ?: 0f,
                    standPrice = it.price?.standardPrice ?: 0f,
                    lowestPrice = it.price?.lowestPrice ?: 0f,
                    serviceFee = it.price?.serviceAddFee ?: 0f,
                    language =  it.films.film.lang
                )
            } ?: listOf()
        )
    }

    override fun scheduleSeatMap(input: ScheduleSeatMapInput): ScheduleSeatMapOutput {
        val params = listOf(
            RequestClient.Paramter("pFeatureAppNo", XMLType.XSD_STRING, input.sessionCode),
            RequestClient.Paramter("pStatus", XMLType.XSD_STRING, "All"),
        )

        val data = RequestClient.send("QueryPlanSeat", params, configData)
            .parseJson(QueryPlanSeat::class.java)
            .also { it.queryPlanSeatResult.checkApiStatus() }

        val seatMap: MutableMap<String, QueryPlanSeat.QueryPlanSeatResult.PlanSiteStates.PlanSiteState> = mutableMapOf()
        data.queryPlanSeatResult.planSiteStates.planSiteState.forEach {
            seatMap["${it.rowNum}-${it.columnNum}"] = it
        }

        val screenSeatData = querySeatInfo(input.hallCode)

        val maxRow = screenSeatData.querySeatInfoResult.screenSites.screenSite.maxOfOrNull { it.yCoord.toInt() } ?: 0
        val maxCol = screenSeatData.querySeatInfoResult.screenSites.screenSite.maxOfOrNull { it.xCoord.toInt() } ?: 0

        val screenSeatMap: MutableMap<String, QuerySeatInfo.QuerySeatInfoResult.ScreenSite> = mutableMapOf()
        val seatGroupMap: MutableMap<String, MutableList<String>> = mutableMapOf()
        screenSeatData.querySeatInfoResult.screenSites.screenSite.forEach {
            screenSeatMap["${it.yCoord}-${it.xCoord}"] = it

            if (seatGroupMap[it.groupCode] == null) {
                seatGroupMap[it.groupCode] = mutableListOf(it.xCoord)
            } else {
                seatGroupMap[it.groupCode]?.add(it.xCoord)
            }
        }

        val seatData: MutableList<MutableList<ScheduleSeatMapOutput.Seat>> = mutableListOf()
        for (row in 0 .. maxRow) {
            val rows = mutableListOf<ScheduleSeatMapOutput.Seat>()
            seatData.add(rows)
            for (col in 0 .. maxCol) {
                val seat = screenSeatMap["$row-$col"]
                val groupSeats = seatGroupMap[seat?.groupCode]
                val groupSeatMax = groupSeats?.maxOrNull()
                val groupSeatMin = groupSeats?.minOrNull()

                val seatInfo = ScheduleSeatMapOutput.Seat(
                    seat?.seatCode ?: "",
                    seat?.seatCode ?: "",
                    seat?.rowNum ?: "",
                    seat?.columnNum ?: "",
                    row,
                    col,
                    seatStatusFormat(seatMap["${seat?.rowNum}-${seat?.columnNum}"]),
                    when {
                        groupSeats != null && groupSeats.size == 2 && seat?.xCoord == groupSeatMin -> -1
                        groupSeats != null && groupSeats.size == 1 -> 0
                        groupSeats != null && groupSeats.size == 2 && seat?.xCoord == groupSeatMax -> 1
                        else -> 0
                    }
                )
                seatData[row].add(seatInfo)
            }
        }

        return ScheduleSeatMapOutput(seatData)
    }

    override fun halls(): HallOutput {
        val data = RequestClient.send("QueryCinemaInfo", listOf(), configData)
            .parseJson(QueryCinemaInfo::class.java)
            .also {
                it.queryCinemaInfoResult.checkApiStatus()
            }

        return HallOutput(
            list = data.queryCinemaInfoResult.cinema.screens.screenVO.map {
                HallOutput.Hall(
                    name = it.screenName,
                    code = it.screenCode,
                    type = it.type,
                    seatCount = it.seatCount,
                )
            }
        )
    }

    override fun seatLock(input: SeatLockInput): SeatLockOutput {
        val lockSeatXml = XmlDataManager.seatLock(configData, input)
        val res = RequestClient.sendXml(
            "LockSeat",
            listOf(
                RequestClient.Paramter("LockSeatXml", XMLType.XSD_STRING, lockSeatXml),
            ),
        ).parseJson(SeatLock::class.java)
            .also { it.lockSeatResult.checkApiStatus() }

        return SeatLockOutput(
            id = res.lockSeatResult.orderCode,
            outLockNo = input.outLockNo,
            lockDuration = res.lockSeatResult.lockLength.toIntOrNull() ?: 0,
        )
    }

    override fun seatUnlock(input: SeatUnlockInput): SeatUnlockOutput {
        val lockSeatXml = XmlDataManager.seatUnlock(configData, input)
        val res = RequestClient.sendXml(
            "ReleaseSeat",
            listOf(
                RequestClient.Paramter("ReleaseSeatXml", XMLType.XSD_STRING, lockSeatXml),
            ),
        ).parseJson(SeatUnlock::class.java).also {
            it.releaseSeatResult.checkApiStatus()
        }

        return SeatUnlockOutput(true)
    }

    override fun orderTicketSubmit(input: OrderTicketSubmitInput): OrderTicketSubmitOutput {
        val xml = XmlDataManager.orderTicketSubmit(configData, input)
        val res = RequestClient.sendXml(
            "SubmitOrder",
            listOf(
                RequestClient.Paramter("SubmitOrderXml", XMLType.XSD_STRING, xml),
            )
        ).parseJson(SubmitOrder::class.java)

        res.submitOrderResult.checkApiStatus()

        val seatInfo = res.submitOrderResult.seatInfos?.seatInfo
            ?: throw ApiStatusException(res.submitOrderResult.resultCode.toString(), res.submitOrderResult.message)

        val seatList = if (input.seatList.size > 1) {
            seatInfo.toJson().parseJson(object : TypeToken<List<SeatInfo>>() {})
        } else {
            listOf(seatInfo.toJson().parseJson(object : TypeToken<SeatInfo>() {}))
        }

        return OrderTicketSubmitOutput(
            orderNo = res.submitOrderResult.orderCode,
            takeCodeList = listOf(
                OrderTicketSubmitOutput.TakeCode("序列号", res.submitOrderResult.printNo),
                OrderTicketSubmitOutput.TakeCode("验证码", res.submitOrderResult.verifyCode),
            ),
            seatList = seatList.map {
                OrderTicketSubmitOutput.Seat(
                    id = it.seatCode,
                    code = it.seatCode,
                    filmTicketCode = it.filmTicketCode,
                    ticketInfoCode = it.ticketInfoCode,
                )
            }
        )
    }

    override fun orderTicketStatus(input: OrderTicketStatusInput): OrderTicketStatusOutput {
        val data = RequestClient.send("QueryOrderStatus", listOf(
            RequestClient.Paramter("pOrderCode", XMLType.XSD_STRING, input.orderNo),
        ), configData)
            .parseJson(QueryOrderStatus::class.java)
            .also { it.queryOrderStatusResult.checkApiStatus() }

        val seatInfo = data.queryOrderStatusResult.seatInfos?.seatInfo
            ?: throw ApiStatusException(data.queryOrderStatusResult.resultCode.toString(), data.queryOrderStatusResult.message)

        val seatList = try {
            seatInfo.toJson().parseJson(object : TypeToken<List<SeatInfo>>() {})
        } catch (e: ParseDataException) {
            listOf(seatInfo.toJson().parseJson(object : TypeToken<SeatInfo>() {}))
        }

        val status = when (data.queryOrderStatusResult.orderStatus) {
            "0" -> OrderTicketStatus.SUCCESS
            "1" -> OrderTicketStatus.FAIL
            "2" -> OrderTicketStatus.CANCEL
            else -> OrderTicketStatus.FAIL
        }

        return OrderTicketStatusOutput(
            status,
            takeCodes = listOf(
                OrderTicketStatusOutput.TakeCode("序列号", data.queryOrderStatusResult.printNo),
                OrderTicketStatusOutput.TakeCode("验证码", data.queryOrderStatusResult.verifyCode),
            ),
            seatList = seatList.map {
                OrderTicketStatusOutput.Seat(
                    id = it.seatCode,
                    code = it.seatCode,
                    filmTicketCode = it.filmTicketCode,
                    ticketInfoCode = it.ticketInfoCode,
                )
            }
        )
    }

    override fun orderTicketInfo(input: OrderTicketInfoInput): OrderTicketInfoOutput {
        val xml = XmlDataManager.orderTicketInfo(configData, input)
        val res = RequestClient.sendXml(
            "QueryTicketInfo",
            listOf(
                RequestClient.Paramter("QueryTicketInfoXml", XMLType.XSD_STRING, xml),
            )
        ).parseJson(QueryTicketInfo::class.java)
            .also { it.queryTicketInfoResult.checkApiStatus() }

        val ticketData =  res.queryTicketInfoResult.tickets!!.ticket

        val ticketList = try {
            ticketData.toJson().parseJson(object : TypeToken<List<QueryTicketInfo.Tickets.Ticket>>() {})
        } catch (e: ParseDataException) {
            listOf(ticketData.toJson().parseJson(object : TypeToken<QueryTicketInfo.Tickets.Ticket>() {}))
        }

        val firstTicket = ticketList.first()

        return OrderTicketInfoOutput(
            cinemaName = firstTicket.cinemaName,
            startTime = firstTicket.startTime,
            screenCode = firstTicket.screenCode,
            screenName = firstTicket.screenName,
            sessionCode = firstTicket.featureAppNo,
            filmCode = firstTicket.filmCode,
            filmName = firstTicket.filmName,
            tickets = ticketList.map {
                val seatSplit = it.seatCode.split("#")
                OrderTicketInfoOutput.Ticket(
                    qrCode = it.ticketInfoCode,
                    ticketCode = it.ticketCode,
                    printFlag = it.printFlag.toString(),
                    row = seatSplit[1],
                    col = seatSplit[2],
                    seatCode = it.seatCode,
                    seat = it.seatName,
                    ticketPrice = it.price,
                    service = it.service
                )
            },
            printId = firstTicket.printNo,
            ""
        )
    }

    override fun orderTicketRefund(input: OrderTicketRefundInput): OrderTicketRefundOutput {
        val codes = input.code.split("|")
        val printNo = codes[0]
        val verifyCode = codes[1]

        val data = RequestClient.send("CancelOrder", listOf(
            RequestClient.Paramter("pPrintNo", XMLType.XSD_STRING, printNo),
            RequestClient.Paramter("pVerifyCode", XMLType.XSD_STRING, verifyCode),
        ), configData)
            .parseJson(CancelOrder::class.java)
            .also { it.cancelOrderResult.checkApiStatus() }

        println(data)

        return OrderTicketRefundOutput(true)
    }

    /**
     * 获取影片信息
     *
     * @param startDate
     * @param endDate
     *
     * @return [QueryFilmList]
     */
    private fun queryFilmInfoByDatePeriod(startDate: String, endDate: String): QueryFilmList {
        val params = listOf(
            RequestClient.Paramter("pPlanStartDate", XMLType.XSD_STRING, startDate),
            RequestClient.Paramter("pPlanEndDate", XMLType.XSD_STRING, endDate),
        )

        return RequestClient.send("QueryFilmInfoByDatePeriod", params, configData)
            .parseJson(QueryFilmList::class.java)
            .also {
                it.queryFilmInfoByDatePeriodResult.checkApiStatus()
            }
    }

    /**
     * 查询影厅座位信息
     *
     * @param screenCode 影厅编码
     *
     * @return [QuerySeatInfo]
     */
    private fun querySeatInfo(screenCode: String): QuerySeatInfo {
        val params = listOf(
            RequestClient.Paramter("pScreenCode", XMLType.XSD_STRING, screenCode),
        )

        return RequestClient.send("QuerySeatInfo", params, configData)
            .parseJson(QuerySeatInfo::class.java)
            .also {
                it.querySeatInfoResult.checkApiStatus()
            }
    }

    /**
     * 检查接口响应数据状态
     */
    private fun BaseResult.checkApiStatus() {
        if (resultCode != 0) {
            throw ApiStatusException(resultCode.toString(), message)
        }
    }

    /**
     * 格式化座位状态
     *
     * @param seat 座位信息
     *
     * @return 状态
     */
    private fun seatStatusFormat(seat: QueryPlanSeat.QueryPlanSeatResult.PlanSiteStates.PlanSiteState?): Short {
        return when (seat?.status) {
            "Available" -> SeatStatus.AVAILABLE.value
            "Locked" -> SeatStatus.LOCKED.value
            "Sold" -> SeatStatus.SOLD.value
            "Booked" -> SeatStatus.BOOKED.value
            "Unavailable" -> SeatStatus.UNAVAILABLE.value
            "Isolate" -> SeatStatus.ISOLATE.value
            else -> SeatStatus.BLANK.value
        }
    }
}